package cn.lingyangwl.agile.pay.pay.process.refund;

import cn.lingyangwl.agile.pay.common.assembly.RefundAssembly;
import cn.lingyangwl.agile.pay.common.constants.PayNodeEnum;
import cn.lingyangwl.agile.pay.common.exception.ChannelException;
import cn.lingyangwl.agile.pay.common.manager.PayMqSender;
import cn.lingyangwl.agile.pay.common.model.mq.RefundOrderMQ;
import cn.lingyangwl.agile.pay.common.service.PayNodeService;
import cn.lingyangwl.agile.pay.common.service.PayOrderService;
import cn.lingyangwl.agile.pay.common.service.RefundOrderService;
import cn.lingyangwl.agile.pay.common.utils.AmountUtils;
import cn.lingyangwl.agile.pay.common.utils.SeqUtils;
import cn.lingyangwl.agile.pay.model.entity.*;
import cn.lingyangwl.agile.pay.pay.channel.AbstractRefundService;
import cn.lingyangwl.agile.pay.pay.channel.IRefundService;
import cn.lingyangwl.agile.pay.pay.model.bo.MchAppConfigContext;
import cn.lingyangwl.agile.pay.pay.model.rqrs.ChannelRetMsgExt;
import cn.lingyangwl.agile.pay.pay.service.ConfigContextQueryService;
import cn.lingyangwl.agile.pay.pay.service.PayMchNotifyService;
import cn.lingyangwl.framework.core.response.Resp;
import cn.lingyangwl.framework.core.utils.IpUtils;
import cn.lingyangwl.framework.tool.core.StringUtils;
import cn.lingyangwl.framework.tool.core.exception.BizException;
import cn.hutool.core.date.DateUtil;
import cn.lingyangwl.agile.common.security.utils.SecurityUtils;
import cn.lingyangwl.agile.pay.model.rqrs.pay.refund.RefundOrderRQ;
import cn.lingyangwl.agile.pay.model.rqrs.pay.refund.RefundOrderRS;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Optional;

/**
 * @author shenguangyang
 */
@Slf4j
@Service
public class RefundOrderProcess {
    @Resource
    private RefundAssembly refundAssembly;
    @Resource
    private PayOrderService payOrderService;
    @Resource
    private RefundOrderService refundOrderService;
    @Resource
    private PayMchNotifyService payMchNotifyService;
    @Resource
    private PayNodeService payNodeService;
    @Resource
    private ConfigContextQueryService configContextQueryService;
    @Resource
    private PayMqSender payMqSender;

    /**
     * 申请退款
     */
    public Resp<?> refundOrder(RefundOrderRQ rq) {
        if (rq.getRefundAmount().compareTo(new BigDecimal("0.01")) < 0) {
            throw new BizException("退款金额请大于1分");
        }

        RefundOrder refundOrder = null;
        PayNode payNode = PayNode.builder()
                .createdUid(SecurityUtils.getUserId())
                .titleCode(PayNodeEnum.REFUND_OK.getCode()).build();

        try {

            if (StringUtils.isAllEmpty(rq.getMchOrderNo(), rq.getPayOrderNo())) {
                throw new BizException("mchOrderNo 和 payOrderNo不能同时为空");
            }

            PayOrder payOrder = payOrderService.getMchOrder(rq.getMchNo(), rq.getPayOrderNo(), rq.getMchOrderNo());
            if (payOrder == null) {
                throw new BizException("退款订单不存在");
            }
            payNode.setPayOrderNo(payOrder.getPayOrderNo());

            if (!PayOrder.STATE_SUCCESS.equals(payOrder.getStatus())) {
                throw new BizException("订单状态不正确， 无法完成退款");
            }

            if (PayOrder.REFUND_STATE_ALL.equals(payOrder.getRefundStatus())
                    || payOrder.getRefundAmount().compareTo(payOrder.getPayAmount()) >= 0) {
                throw new BizException("订单已全额退款，本次申请失败");
            }

            if (AmountUtils.add(payOrder.getRefundAmount(), rq.getRefundAmount()).compareTo(payOrder.getPayAmount()) > 0) {
                throw new BizException("申请金额超出订单可退款余额，请检查退款金额");
            }

            if (refundOrderService.count(RefundOrder.lqw().eq(RefundOrder::getPayOrderNo, payOrder.getPayOrderNo())
                    .eq(RefundOrder::getStatus, RefundOrder.STATE_ING)) > 0) {
                throw new BizException("已有订单正在申请退款，请稍后再试");
            }

            // 全部退款金额 （退款订单表）
            BigDecimal sumSuccessRefundAmount = Optional.ofNullable(refundOrderService.sumSuccessRefundAmount(payOrder.getPayOrderNo()))
                    .orElse(BigDecimal.ZERO);
            if (sumSuccessRefundAmount.compareTo(payOrder.getPayAmount()) >= 0) {
                throw new BizException("退款单已完成全部订单退款，本次申请失败");
            }

            if (AmountUtils.add(sumSuccessRefundAmount, rq.getRefundAmount()).compareTo(payOrder.getPayAmount()) > 0) {
                throw new BizException("申请金额超出订单可退款余额，请检查退款金额");
            }

            String mchNo = rq.getMchNo();
            String appId = rq.getAppId();

            // 校验退款单号是否重复
            if (refundOrderService.count(RefundOrder.lqw().eq(RefundOrder::getMchNo, mchNo)
                    .eq(RefundOrder::getMchRefundNo, rq.getMchRefundNo())) > 0) {
                throw new BizException("商户退款订单号[" + rq.getMchRefundNo() + "]已存在");
            }

            if (StringUtils.isNotEmpty(rq.getNotifyUrl()) && !StringUtils.isAvailableUrl(rq.getNotifyUrl())) {
                throw new BizException("异步通知地址协议仅支持http:// 或 https:// !");
            }

            // 获取支付参数 (缓存数据) 和 商户信息
            MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, appId);
            if (mchAppConfigContext == null) {
                throw new BizException("获取商户应用信息失败");
            }

            MchInfo mchInfo = mchAppConfigContext.getMchInfo();
            MchApp mchApp = mchAppConfigContext.getMchApp();

            // 获取退款接口
            IRefundService refundService = AbstractRefundService.getService(payOrder.getIfCode());
            if (refundService == null) {
                throw new BizException("当前通道不支持退款！");
            }

            refundOrder = genRefundOrder(rq, payOrder, mchInfo, mchApp);

            // 退款单入库 退款单状态：生成状态  此时没有和任何上游渠道产生交互。
            refundOrderService.save(refundOrder);

            // 调起退款接口
            ChannelRetMsgExt channelRetMsgExt = refundService.refund(rq, refundOrder, payOrder, mchAppConfigContext);
            if (channelRetMsgExt.isFail()) {
                payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
                return Resp.fail(channelRetMsgExt.getChannelErrMsg());
            }

            // 发送延时消息, 反向查询订单是否已经退款成功
            payMqSender.send(RefundOrderMQ.build(refundOrder.getRefundOrderId()));

            // 处理退款单状态
            this.processChannelMsg(payNode, channelRetMsgExt, refundOrder);

            RefundOrderRS bizRes = refundAssembly.toRefundOrderRS(refundOrder);

            return Resp.ok(bizRes);
        } catch (BizException e) {
            payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
            return Resp.fail(e.getMessage());
        } catch (ChannelException e) {
            payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
            // 处理上游返回数据
            this.processChannelMsg(payNode, e.getChannelRetMsgExt(), refundOrder);

            if (e.getChannelRetMsgExt().getChannelState() == ChannelRetMsgExt.ChannelState.SYS_ERROR) {
                return Resp.fail(e.getMessage());
            }

            RefundOrderRS bizRes = refundAssembly.toRefundOrderRS(refundOrder);
            return Resp.fail(-1, "退款失败", bizRes);
        } catch (Exception e) {
            log.error("系统异常：", e);
            return Resp.fail();
        } finally {
            payNodeService.savePayNode(payNode);
        }

    }

    private RefundOrder genRefundOrder(RefundOrderRQ rq, PayOrder payOrder, MchInfo mchInfo, MchApp mchApp) {

        Date nowTime = new Date();
        RefundOrder refundOrder = new RefundOrder();
        refundOrder.setRefundOrderId(SeqUtils.genRefundOrderId()); //退款订单号
        refundOrder.setPayOrderNo(payOrder.getPayOrderNo()); //支付订单号
        refundOrder.setMchOrderNo(payOrder.getMchOrderNo()); // 商户订单号
        refundOrder.setChannelPayOrderNo(payOrder.getChannelOrderNo()); //渠道支付单号
        refundOrder.setMchNo(mchInfo.getMchNo()); // 商户号
        refundOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
        refundOrder.setAppId(mchApp.getId()); //商户应用ID
        refundOrder.setMchName(mchInfo.getMchShortName()); //商户名称
        refundOrder.setMchType(mchInfo.getType()); //商户类型
        refundOrder.setMchRefundNo(rq.getMchRefundNo()); //商户退款单号
        refundOrder.setWayCode(payOrder.getWayCode()); //支付方式代码
        refundOrder.setIfCode(payOrder.getIfCode()); //支付接口代码
        refundOrder.setPayAmount(payOrder.getPayAmount()); //支付金额,单位分
        refundOrder.setRefundAmount(rq.getRefundAmount()); //退款金额,单位分
        refundOrder.setCurrency(rq.getCurrency()); //三位货币代码,人民币:cny
        refundOrder.setStatus(RefundOrder.STATE_INIT); //退款状态:0-订单生成,1-退款中,2-退款成功,3-退款失败
        refundOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), IpUtils.getHostIp())); //客户端IP
        refundOrder.setRefundReason(rq.getRefundReason()); //退款原因
        refundOrder.setChannelOrderNo(null); //渠道订单号
        refundOrder.setErrCode(null); //渠道错误码
        refundOrder.setErrMsg(null); //渠道错误描述
        refundOrder.setChannelExtra(rq.getChannelExtra()); //特定渠道发起时额外参数
        refundOrder.setNotifyUrl(rq.getNotifyUrl()); //通知地址
        refundOrder.setExtParam(rq.getExtParam()); //扩展参数
        refundOrder.setExpiredTime(DateUtil.offsetHour(nowTime, 2)); //订单超时关闭时间 默认两个小时
        refundOrder.setSuccessTime(null); //订单退款成功时间
        refundOrder.setCreatedAt(nowTime); //创建时间

        return refundOrder;
    }


    /**
     * 处理返回的渠道信息，并更新退款单状态
     * payOrder将对部分信息进行 赋值操作。
     */
    private void processChannelMsg(PayNode payNode, ChannelRetMsgExt channelRetMsgExt, RefundOrder refundOrder) {
        // 对象为空 || 上游返回状态为空， 则无需操作
        if (channelRetMsgExt == null || channelRetMsgExt.getChannelState() == null) {
            payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
            return;
        }

        // 明确成功
        if (ChannelRetMsgExt.ChannelState.CONFIRM_SUCCESS == channelRetMsgExt.getChannelState()) {
            this.updateInitOrderStateThrowException(RefundOrder.STATE_SUCCESS, refundOrder, channelRetMsgExt);
            payMchNotifyService.refundOrderNotify(refundOrder);
            payNode.setTitleCode(PayNodeEnum.REFUND_OK.getCode());
            // 明确失败
        } else if (ChannelRetMsgExt.ChannelState.CONFIRM_FAIL == channelRetMsgExt.getChannelState()) {
            this.updateInitOrderStateThrowException(RefundOrder.STATE_FAIL, refundOrder, channelRetMsgExt);
            payMchNotifyService.refundOrderNotify(refundOrder);
            payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
            // 上游处理中 || 未知 || 上游接口返回异常  退款单为退款中状态
        } else if (ChannelRetMsgExt.ChannelState.WAITING == channelRetMsgExt.getChannelState() ||
                ChannelRetMsgExt.ChannelState.UNKNOWN == channelRetMsgExt.getChannelState() ||
                ChannelRetMsgExt.ChannelState.API_RET_ERROR == channelRetMsgExt.getChannelState()
        ) {
            this.updateInitOrderStateThrowException(RefundOrder.STATE_ING, refundOrder, channelRetMsgExt);
            payNode.setTitleCode(PayNodeEnum.REFUND_INIT.getCode());
            // 系统异常：  退款单不再处理。  为： 生成状态
        } else if (ChannelRetMsgExt.ChannelState.SYS_ERROR == channelRetMsgExt.getChannelState()) {
            payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
        } else {
            payNode.setTitleCode(PayNodeEnum.REFUND_FAIL.getCode());
            throw new BizException("ChannelState 返回异常！");
        }

    }


    /**
     * 更新退款单状态 --》 退款单生成--》 其他状态  (向外抛出异常)
     */
    private void updateInitOrderStateThrowException(String orderStatus, RefundOrder refundOrder, ChannelRetMsgExt channelRetMsgExt) {
        refundOrder.setStatus(orderStatus);
        refundOrder.setChannelOrderNo(channelRetMsgExt.getChannelOrderId());
        refundOrder.setErrCode(channelRetMsgExt.getChannelErrCode());
        refundOrder.setErrMsg(channelRetMsgExt.getChannelErrMsg());


        boolean isSuccess = refundOrderService.updateInit2Ing(refundOrder.getRefundOrderId(), channelRetMsgExt.getChannelOrderId());
        if (!isSuccess) {
            throw new BizException("更新退款单异常!");
        }

        isSuccess = refundOrderService.updateIng2SuccessOrFail(refundOrder.getRefundOrderId(), refundOrder.getStatus(),
                channelRetMsgExt.getChannelOrderId(), channelRetMsgExt.getChannelErrCode(), channelRetMsgExt.getChannelErrMsg());
        if (!isSuccess) {
            throw new BizException("更新退款单异常!");
        }
    }
}
